-- =================== -*- mode: sql; -*- ===================
-- table_functions.sql
--
-- Test for enhancements to table function support
-- ===================
CREATE SCHEMA table_function;
SET search_path TO table_function, public;

CREATE EXTENSION IF NOT EXISTS gp_inject_fault;

CREATE TABLE example(a int, b text) DISTRIBUTED by (a);
COPY example FROM stdin;
1	 value1.1/4
2	 value2.1/3
3	 value3.1/2
4	 value4.1/1
1	 value1.2/4
2	 value2.2/3
3	 value3.2/2
1	 value1.3/4
2	 value2.3/3
1	 value1.4/4
\.

CREATE TABLE history(id integer, time timestamp) DISTRIBUTED BY (id);
COPY history FROM stdin;
1	 2011/08/21 10:15:02am
1	 2011/08/21 10:15:30am
1	 2011/08/22 10:15:04am
1	 2011/08/22 10:16:10am
2	 2011/08/21 10:15:02am
2	 2011/08/21 10:15:02pm
2	 2011/08/21 10:16:02am
2	 2011/08/21 10:16:02pm
3	 2011-08-19 19:05:13
3	 2011-08-19 19:06:50
3	 2011-08-19 19:07:35
3	 2011-08-19 19:08:18
3	 2011-08-19 19:09:07
3 	 2011-08-20 10:07:10
3	 2011-08-20 10:07:35
3	 2011-08-20 10:11:29
3	 2011-08-20 10:17:10
3	 2011-08-20 10:17:42
\.

CREATE TABLE example_r AS SELECT * FROM example DISTRIBUTED RANDOMLY;

CREATE VIEW example_v AS SELECT * FROM example;

/* Attempt to get relatively stable plans */
set gp_segments_for_planner=8;
analyze example;
analyze example_r;


/* Create some normal scalar input / scalar output functions */
CREATE FUNCTION scalar_1(int) RETURNS int
    AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;

CREATE FUNCTION scalar_2(IN int) RETURNS int
    AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;

CREATE FUNCTION scalar_3(IN a int, OUT b INT)
    AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;

CREATE FUNCTION scalar_4(INOUT x int)
    AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;

CREATE FUNCTION scalar_5(anyelement) RETURNS anyelement
    AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;


/* Create some normal scalar input / table output functions */
CREATE FUNCTION scalar_tf_1(int) RETURNS TABLE(a int, b text)
    AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;

CREATE FUNCTION scalar_tf_2(IN int) RETURNS TABLE(a int, b text)
    AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;

CREATE FUNCTION scalar_tf_3(IN a int) RETURNS TABLE(a int, b text)
    AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;

CREATE FUNCTION scalar_tf_4(IN a int, OUT a int, OUT b text) RETURNS SETOF RECORD
    AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;

CREATE FUNCTION scalar_tf_5(IN a int) RETURNS SETOF RECORD
    AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;

CREATE FUNCTION scalar_tf_6(IN a anyelement) RETURNS SETOF example
    AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;


/* CREATE some multiset input table functions */

/* scalar value outputs */
CREATE FUNCTION multiset_scalar_null(anytable) RETURNS int
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_scalar_null' LANGUAGE C READS SQL DATA;

CREATE FUNCTION multiset_scalar_value(anytable) RETURNS int
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_scalar_value' LANGUAGE C READS SQL DATA;

CREATE FUNCTION multiset_scalar_tuple(anytable) RETURNS example
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_scalar_tuple' LANGUAGE C READS SQL DATA;


/* set value outputs */
CREATE FUNCTION multiset_setof_null(anytable) RETURNS setof int
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_setof_null' LANGUAGE C READS SQL DATA;

CREATE FUNCTION multiset_setof_value(anytable) RETURNS setof int
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_setof_value' LANGUAGE C READS SQL DATA;


/* Bunches of different ways of saying "returns a setof rows */
CREATE FUNCTION multiset_materialize_good(anytable)
	   RETURNS TABLE(a int, b text)
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_materialize_good' LANGUAGE C READS SQL DATA;

CREATE FUNCTION multiset_materialize_bad(anytable)
	   RETURNS TABLE(a int, b text)
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_materialize_bad' LANGUAGE C READS SQL DATA;

CREATE FUNCTION multiset_1(a anytable) RETURNS TABLE(a int, b text)
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_example' LANGUAGE C READS SQL DATA;

CREATE FUNCTION multiset_2(a anytable) RETURNS TABLE(a int, b text)
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_example' LANGUAGE C READS SQL DATA;

CREATE FUNCTION multiset_3(a anytable, out a int, out b text) RETURNS SETOF RECORD
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_example' LANGUAGE C READS SQL DATA;

CREATE FUNCTION multiset_4(a anytable) RETURNS SETOF RECORD
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_example' LANGUAGE C READS SQL DATA;

CREATE FUNCTION multiset_5(a anytable) RETURNS SETOF example
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_example' LANGUAGE C READS SQL DATA;

CREATE FUNCTION multiset_6(a anytable) RETURNS SETOF record
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_example' LANGUAGE C READS SQL DATA;


/* Negative test cases around CREATE FUNCTION */

/* ERROR: TABLE output not allowed with OUT parameters */
CREATE FUNCTION error(OUT a int) RETURNS TABLE(a int, b text)
    AS $$ SELECT * from example $$ LANGUAGE SQL READS SQL DATA;

/* ERROR: Multiple multiset inputs not supported */
CREATE FUNCTION error(a anytable, b anytable) RETURNS TABLE(a int, b text)
    AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;

/* ERROR: Multiset outputs not supported */
CREATE FUNCTION error(OUT anytable)
    AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION error() RETURNS TABLE(x anytable)
    AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;

/* ERROR: SETOF as IN/OUT/TABLE parameters is not supported */
CREATE FUNCTION error(OUT setof example)
    AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION error(IN setof example)
    AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION error(INOUT setof example)
    AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION error() RETURNS TABLE(a setof example)
    AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;

/* ERROR: anytable cannot have default value */
CREATE FUNCTION error(a anytable DEFAULT TABLE(select 1,'test')) RETURNS TABLE(a int, b text)
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_example' LANGUAGE C;

/* Negative test cases around the "anytable" type */
CREATE TABLE fail(x anytable);
CREATE TYPE  fail AS (x anytable);


/* Observe how they are represented in the catalog */
select
  p.proname,
  p.oid::regproc,
  p.oid::regprocedure,
  p.proretset,
  (p.proargtypes::regtype[])[0:0],
  p.prorettype::regtype,
  p.proargmodes
FROM pg_proc p join pg_namespace n ON (p.pronamespace = n.oid)
WHERE n.nspname = 'table_function'
ORDER BY p.proname;


/* Observe how psql reports them */
\df (scalar_*|multiset_*)

-- Normal function use:

-- Normal scalar input / scalar output functions
-- begin equivalent
SELECT scalar_1(5) as b;
SELECT scalar_2(5) as b;
SELECT scalar_3(5) as b;
SELECT scalar_4(5) as b;
SELECT scalar_5(5) as b;
SELECT scalar_1((select 5)) as b;
SELECT scalar_2((select 5)) as b;
SELECT scalar_3((select 5)) as b;
SELECT scalar_4((select 5)) as b;
SELECT scalar_5((select 5)) as b;
SELECT b FROM scalar_1(5) as b;
SELECT b FROM scalar_2(5) as b;
SELECT b FROM scalar_3(5);
SELECT x as b FROM scalar_4(5);
SELECT b FROM scalar_5(5) as b;
SELECT b FROM scalar_1((select 5)) as b;
SELECT b FROM scalar_2((select 5)) as b;
SELECT b FROM scalar_3((select 5));
SELECT x as b FROM scalar_4((select 5));
SELECT b FROM scalar_5((select 5)) as b;
-- end equivalent

-- ERROR cases for simple scalar functions
SELECT scalar_1((select 1, 2));  -- subquery returns multiple columns
SELECT scalar_1((select 1 union select 2));  -- subquery returns multiple rows
SELECT scalar_1(TABLE(select 1));  -- TableValue expression does not match type
SELECT scalar_5(TABLE(select 1));  -- TableValue shouldn't match "anyelement"


-- Normal scalar input / table output functions
-- begin equivalent */
SELECT row(a+5, b)::example from example;
SELECT scalar_tf_1(5);
SELECT scalar_tf_2(5);
SELECT scalar_tf_3(5);
SELECT scalar_tf_4(5);
SELECT scalar_tf_5(5);
SELECT scalar_tf_6(5);
SELECT scalar_tf_1((select 5));
SELECT scalar_tf_2((select 5));
SELECT scalar_tf_3((select 5));
SELECT scalar_tf_4((select 5));
SELECT scalar_tf_5((select 5));
SELECT scalar_tf_6((select 5));
-- end equivalent

-- begin equivalent
SELECT a+5 as a, b from example order by a, b;
SELECT * FROM scalar_tf_1(5) order by a, b;
SELECT * FROM scalar_tf_2(5) order by a, b;
SELECT * FROM scalar_tf_3(5) order by a, b;
SELECT * FROM scalar_tf_4(5) order by a, b;
SELECT * FROM scalar_tf_5(5) e(a int, b text)  order by a, b;
SELECT * FROM scalar_tf_6(5) order by a, b;
SELECT * FROM scalar_tf_1((select 5)) order by a, b;
SELECT * FROM scalar_tf_2((select 5)) order by a, b;
SELECT * FROM scalar_tf_3((select 5)) order by a, b;
SELECT * FROM scalar_tf_4((select 5)) order by a, b;
SELECT * FROM scalar_tf_5((select 5)) e(a int, b text)  order by a, b;
SELECT * FROM scalar_tf_6((select 5)) order by a, b;
/*
  ERROR:  Statement Error, writer gang busy: possible attempt to execute volatile function in unsupported context.

    Fixed if we switch away from SQL language functions?

SELECT (scalar_tf_1(5)).*;
SELECT (scalar_tf_2(5)).*;
SELECT (scalar_tf_3(5)).*;
SELECT (scalar_tf_4(5)).*;
SELECT (scalar_tf_5(5)).*;
SELECT (scalar_tf_6(5)).*;
SELECT (scalar_tf_1((select 5))).*;
SELECT (scalar_tf_2((select 5))).*;
SELECT (scalar_tf_3((select 5))).*;
SELECT (scalar_tf_4((select 5))).*;
SELECT (scalar_tf_5((select 5))).*;
SELECT (scalar_tf_6((select 5))).*;
*/
-- end equivalent

-- ERROR cases for simple table output functions
SELECT scalar_tf_1((select 1, 2));  -- subquery returns multiple columns
SELECT scalar_tf_1((select 1 union select 2));  -- subquery returns multiple rows
SELECT scalar_tf_1(TABLE(select 1));  -- TableValue expression does not match type
SELECT scalar_tf_6(TABLE(select 1));  -- TableValue expression does not anyelement

-- Table Functions   table input / table output
-- begin equivalent
SELECT * FROM example order by a, b;
SELECT * FROM multiset_2( TABLE( SELECT * from example) ) order by a, b;
SELECT * FROM multiset_3( TABLE( SELECT * from example) ) order by a, b;
SELECT * FROM multiset_4( TABLE( SELECT * from example) ) e(a int, b text) order by a, b;
SELECT * FROM multiset_6( TABLE( SELECT * from example) ) e(a int, b text)  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * from example) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example SCATTER by a) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example SCATTER by b) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example SCATTER RANDOMLY) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example ORDER BY a, b SCATTER by a) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example ORDER BY b, a SCATTER by b) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example ORDER BY a, b SCATTER RANDOMLY) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * from example_r) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER by a||b) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER by b, a, a||b) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER RANDOMLY) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * from example_v) )  order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example WHERE a >= (SELECT min(a) FROM example))) order by a,b;
WITH cte AS (SELECT * FROM example) SELECT * FROM multiset_5( TABLE ( SELECT * FROM cte ) )  order by a, b;
WITH cte AS (SELECT * FROM example) SELECT x.* FROM multiset_5( TABLE ( SELECT * FROM cte ) ) x, (SELECT count(*) FROM cte) y order by x.a, x.b;
SELECT x.* FROM multiset_5( TABLE ( SELECT * FROM example ) ) x right join (SELECT 1) y on (true) order by x.a, x.b;
-- end equivalent

-- Table functions with quals
-- begin equivalent
SELECT * FROM example WHERE a = 2;
SELECT * FROM multiset_2( TABLE ( SELECT * FROM example ) ) WHERE a = 2;
SELECT * FROM multiset_2( TABLE ( SELECT * FROM example WHERE a = 2) ) ;
SELECT * FROM multiset_2( TABLE ( SELECT * FROM (SELECT * FROM example) example2 WHERE a = 2) );
SELECT * FROM multiset_2( TABLE ( SELECT * FROM (SELECT * FROM example WHERE a > 1) example2 ) ) WHERE a < 3;
-- end equivalent

-- Table Functions with a focused subplan plan
SELECT * FROM multiset_5( TABLE( SELECT count(*)::integer, 'hello'::text from example_r ) );

SELECT * FROM example_r WHERE (10, 'hello') in (SELECT * FROM multiset_5( TABLE( SELECT count(*)::integer, 'hello'::text from example_r ))) order by a, b;

-- Table Functions + Projection
-- begin equivalent
SELECT b FROM example order by b;
SELECT b FROM multiset_2( TABLE( SELECT * from example) ) order by b;
SELECT b FROM multiset_3( TABLE( SELECT * from example) ) order by b;
SELECT b FROM multiset_4( TABLE( SELECT * from example) ) e(a int, b text) order by b;
SELECT b FROM multiset_6( TABLE( SELECT * from example) ) e(a int, b text)  order by b;
SELECT b FROM multiset_5( TABLE( SELECT * from example) ) order by b;
-- end equivalent

-- Different cases of joins in the subplan
-- begin equivalent
SELECT distinct a.a, b.b from example a, example b where a.a = b.a order by 1,2;
select * from multiset_2( TABLE( SELECT distinct a.a, b.b from example a, example b where a.a = b.a) ) order by 1,2;
select * from multiset_2( TABLE( SELECT distinct a.a, b.b from example a join example b using (a) ) ) order by 1,2;
-- end equivalent

-- Support for different backend code

-- this function always returns a single scalar null value
-- begin equivalent
SELECT null as a;
SELECT * FROM multiset_scalar_null( TABLE(SELECT 1) );
-- end equivalent

-- this function always returns a single scalar value of 42
-- begin equivalent
SELECT 42 as fortytwo;
SELECT * FROM multiset_scalar_value( TABLE(SELECT 42) ) fortytwo;
-- end equivalent


-- This function always returns a single "(1,Example)" tuple
-- begin equivalent
SELECT 1 as a, 'Example' as b;
SELECT * from multiset_scalar_tuple( TABLE(SELECT 1) );
-- end equivalent


-- ERROR: functions returning SETOF x cannot return null
SELECT * FROM multiset_setof_null( TABLE(SELECT 1) );

-- This function returns a setof values [1,2,3]
-- begin equivalent
SELECT a from generate_series(1,3) as a;
SELECT a from multiset_setof_value( TABLE(SELECT 1) ) as a;
-- end equivalent

-- ERROR: Table Functions do not currently support SFRM_Materialize
SELECT * FROM multiset_materialize_good( TABLE( SELECT * from example ) );
SELECT * FROM multiset_materialize_bad( TABLE( SELECT * from example ) );



-- name resolution rules should work correctly between scalar and anytable,
-- i.e. there cannot be any automatic conversion.
CREATE FUNCTION nameres(int) RETURNS int
    AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;
SELECT * from nameres(5);                -- should work
SELECT * from nameres(TABLE(SELECT 5));  -- should fail

CREATE FUNCTION nameres(anytable) RETURNS int
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_scalar_value' LANGUAGE C READS SQL DATA;
SELECT * from nameres(5);                -- should work
SELECT * from nameres(TABLE(SELECT 5));  -- should work

DROP FUNCTION nameres(int);
SELECT * from nameres(5);                -- should fail
SELECT * from nameres(TABLE(SELECT 5));  -- should work

DROP FUNCTION nameres(anytable);
SELECT * from nameres(5);                -- should fail
SELECT * from nameres(TABLE(SELECT 5));  -- should fail

-- Error: anytable does NOT match anyelement
CREATE FUNCTION nameres(anyelement) returns int
  AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;
SELECT * FROM nameres( TABLE( SELECT 1) ); -- should fail
DROP FUNCTION nameres(anyelement);


-- Must support resjunk scatter by clauses
explain SELECT * FROM multiset_5( TABLE( SELECT * FROM example SCATTER BY a+1) );
SELECT * FROM multiset_5( TABLE( SELECT * FROM example SCATTER BY a+1) ) order by a,b;


-- Use of a select list table value expression subquery
-- begin equivalent
SELECT a FROM example ORDER BY a;
SELECT UNNEST(ARRAY(SELECT a FROM example)) ORDER BY 1;
SELECT UNNEST(ARRAY(SELECT a FROM multiset_5( TABLE ( SELECT a, b from example)))) ORDER BY 1;
-- end equivalent


-- Use of a where clause table value expression subquery
-- begin equivalent
SELECT * FROM example order by a, b;
SELECT * FROM example where (a,b) in (select * from example) order by a, b;
SELECT * FROM example where (a,b) in (select * from multiset_5( TABLE(SELECT a, b from example) )) order by a, b;
SELECT * FROM multiset_5( TABLE(SELECT a, b from example)) where (a,b) in (select a,b from example) order by a, b;
-- end equivalent

-- Don't generate unique rowid path if there is a table function scan
SELECT gp_inject_fault_infinite('low_unique_rowid_path_cost', 'skip', dbid) FROM gp_segment_configuration WHERE role = 'p' AND content = -1;
SELECT count(*) FROM multiset_5( TABLE(SELECT a, b from example)) where (a,b) in (select a,b from example);
SELECT gp_inject_fault('low_unique_rowid_path_cost', 'reset', dbid) FROM gp_segment_configuration WHERE role = 'p' AND content = -1;

-- Explain a couple interesting cases
explain SELECT * FROM multiset_5( TABLE (SELECT dbid, gpname FROM gp_id) );
explain SELECT * FROM multiset_5( TABLE (SELECT dbid, gpname FROM gp_id SCATTER BY dbid) );
explain SELECT * FROM multiset_5( TABLE (SELECT dbid, gpname FROM gp_id SCATTER RANDOMLY) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example) );
explain SELECT * from multiset_5( TABLE (SELECT * FROM example ORDER BY a limit 10 ) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example SCATTER BY a) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example SCATTER BY b) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example SCATTER RANDOMLY) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY a, b) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY a, b SCATTER BY a) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY b, a SCATTER BY b) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY b, a SCATTER RANDOMLY) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY a, b LIMIT 10 SCATTER BY a) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY b, a LIMIT 10 SCATTER BY b) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY b, a LIMIT 10 SCATTER RANDOMLY) );
explain SELECT ARRAY(SELECT a FROM multiset_5( TABLE ( SELECT a, b from example)) order by a);
explain SELECT ARRAY(SELECT a FROM multiset_5( TABLE ( SELECT a, b from example order by a)));
explain SELECT * FROM example where (a,b) in (select * from multiset_5( TABLE(SELECT a, b from example) ));
explain SELECT * FROM example where (a,b) in (select * from multiset_5( TABLE(SELECT a, b from example scatter by b) ));
explain SELECT * FROM multiset_5( TABLE(SELECT a, b from example)) where (a,b) in (select a,b from example);
explain SELECT * FROM multiset_5( TABLE(SELECT a, b from example scatter by b)) where (a,b) in (select a,b from example);
explain SELECT * FROM multiset_5( TABLE( SELECT * from example_r) );
explain SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER by a||b) );
explain SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER by b, a, a||b) );
explain SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER RANDOMLY) );
explain SELECT * FROM multiset_5( TABLE( SELECT count(*)::integer, 'hello'::text from example_r ) );
explain SELECT * FROM example_r WHERE (10, 'hello') in (SELECT * FROM multiset_5( TABLE( SELECT count(*)::integer, 'hello'::text from example_r )));
explain SELECT * FROM multiset_5( TABLE( SELECT * from example_v) );
explain SELECT * FROM multiset_5( TABLE( SELECT * FROM example WHERE a >= (SELECT min(a) FROM example)));
explain WITH cte AS (SELECT * FROM example) SELECT * FROM multiset_5( TABLE ( SELECT * FROM cte ) )  order by a, b;
explain WITH cte AS (SELECT * FROM example) SELECT x.* FROM multiset_5( TABLE ( SELECT * FROM cte ) ) x, (SELECT count(*) FROM cte) y order by x.a, x.b;
explain select * from multiset_2( TABLE( SELECT distinct a.a, b.b from example a, example b where a.a = b.a) ) order by 1,2;
explain select * from multiset_2( TABLE( SELECT distinct a.a, b.b from example a join example b using (a) ) ) order by 1,2;
explain select * from multiset_2( TABLE ( SELECT * FROM example WHERE a = 2 ) ) WHERE a = 2;
-- Not rescannable, should produce materialize node
explain SELECT x.* FROM multiset_5( TABLE ( SELECT 1 ) ) x right join (SELECT 1) y on (true);

-- Do an explain analyze while we are at it:
explain analyze SELECT * FROM multiset_5( TABLE( SELECT count(*)::integer, 'hello'::text from example_r scatter randomly) );

-- Test for an old bug in aggregate planning - this used to crash.
select count(*) from multiset_5(table(select * from example)) s ;

-- Test for 'select for update'
begin;
select count(*) from multiset_5(table(select * from example for update)) s ;
abort;

-- ===========================================
-- Test invalid use of table value expressions
-- ===========================================

-- ERROR cases for table functions over table value input
SELECT multiset_2( (SELECT * from example) );       -- not a valid subselect
SELECT multiset_2( (SELECT a from example) );       -- not a TableValue expression / no such function
SELECT multiset_2( TABLE(SELECT * from example) );  -- not in the from clause
SELECT multiset_3( TABLE(SELECT a from example) );  -- not in the from clause
SELECT multiset_5( TABLE(SELECT * from example) ) over (order by 1);
SELECT * from example where 3 = multiset_scalar_value( TABLE(select a from example) ); -- not in from
SELECT count(*) FROM example GROUP BY multiset_scalar_value( TABLE(select a from example) );
SELECT count(*) FROM example HAVING multiset_scalar_value( TABLE(select a from example) ) > 4;
SELECT sum(a) over (partition by multiset_scalar_value( TABLE(select a from example)) order by b) from example;

-- Error: multiset_5 expects (int, text) records as input
SELECT * FROM multiset_5( TABLE(SELECT 1) );

-- ERROR cases for simple TableValue Expressions (anything not a parameter to function)
SELECT TABLE( SELECT * from example );
SELECT ARRAY( TABLE( SELECT * from example) );
SELECT * FROM TABLE( SELECT * from example) );
SELECT * from example where TABLE( SELECT * from example) is not null;
SELECT count(*) FROM example GROUP BY TABLE(select a from example);
SELECT count(*) FROM example HAVING TABLE(select a from example) is not null;
SELECT sum(a) over (partition by TABLE(select * from example) order by b) from example;

-- ERROR raised internally by user code when we don't see an output tuple
-- that matches expectations
SELECT * FROM multiset_4( TABLE( SELECT 1::integer, 'hello'::text) ) as tf(a int);

-- ERROR no support for SELECT INTO in a TableValueExpression
SELECT * FROM multiset_2( TABLE( SELECT * INTO should_error FROM example ) );

-- MPP-14180  ERROR: We don't support TableValueExpressions with external correlations
SELECT * from example e1
WHERE a > (SELECT a FROM multiset_2( TABLE( SELECT * FROM example e2 WHERE e1.a=e2.a))) ;

-- ===========================================
-- Views and view display
-- ===========================================

CREATE VIEW v1 AS
  SELECT * FROM multiset_2( TABLE( SELECT * from example) ) as tf;

SELECT * FROM v1 order by a, b;

\d+ v1

SELECT pg_get_viewdef('v1'::regclass);


CREATE VIEW v2 AS
  SELECT * FROM multiset_2( TABLE( SELECT * from example SCATTER BY a||b, 5) ) as tf;

SELECT * FROM v2 order by a, b;

\d+ v2

SELECT pg_get_viewdef('v2'::regclass);

CREATE VIEW v3 AS
  SELECT * FROM multiset_2( TABLE( SELECT * from example SCATTER RANDOMLY) ) as tf;

SELECT * FROM v3 order by a, b;

\d+ v3

SELECT pg_get_viewdef('v3'::regclass);

DROP view v1;
DROP view v2;
DROP view v3;

-- ========================================
-- Interaction with set returning functions
-- ========================================
CREATE FUNCTION multi_args(a anytable, x int) RETURNS SETOF example
    AS '@abs_builddir@/regress@DLSUFFIX@', 'multiset_example' LANGUAGE C READS SQL DATA;

-- In select list requires some extra setrefs logic in planning
SELECT *, generate_series(1,2) FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), 2);

-- Note the extra "Result" node when the srf is in the select list
explain SELECT *, generate_series(1,2) FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), 2);
explain SELECT * FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), 2);

-- Error: don't support sets as arguments that are not TableValueExpr
SELECT * FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), generate_series(1,2));

-- And use it with a CTAS as well
explain CREATE TABLE example_out AS SELECT * FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), 2);
CREATE TABLE example_out AS SELECT * FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), 2);
SELECT * FROM example_out;
DROP TABLE example_out;

-- ===============
-- Dropped Columns
-- ===============
DROP VIEW example_v;
ALTER TABLE example DROP column a;

-- ERROR: input tuple does not conform to expectations of multiset_5
SELECT * FROM multiset_5( TABLE( SELECT 'hello'::text) ) as tf;

-- However, this should work despite the output tupdesc having a dropped column.
SELECT * FROM multiset_5( TABLE( SELECT 1::integer, 'hello'::text) ) as tf;

-- ============================================
-- Currently on "C" is supported for "anytable"
-- ============================================
-- start_ignore
-- These may have been created by previous test
create language plpythonu;
-- end_ignore

CREATE FUNCTION tf_sql(anytable) returns int AS $$ SELECT 1 $$ language sql CONTAINS SQL;

CREATE FUNCTION tf_pgsql(anytable) returns int AS $$
BEGIN
  RETURN 1;
END;
$$ language plpgsql NO SQL;

CREATE FUNCTION tf_python(anytable) returns int AS $$ return 1 $$ language plpythonu NO SQL;


-- ====================================
-- Test support of dynamic return types (aka the DESCRIBE callback)
-- ====================================

-- Create a function and a describe method
CREATE FUNCTION sessionize_describe(internal)
  RETURNS internal
  AS '@abs_builddir@/regress@DLSUFFIX@', 'describe'
  LANGUAGE C READS SQL DATA;

CREATE FUNCTION sessionize(anytable, interval)
  RETURNS setof record
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA;

-- No dependency yet, all three queries should return 0 rows
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;
SELECT * FROM pg_proc_callback where profnoid not in (select oid from pg_proc);
SELECT * FROM pg_proc_callback where procallback not in (select oid from pg_proc);

-- Should be able to drop the describe function, no ref yet
DROP FUNCTION sessionize_describe(internal);

-- Should fail, no such function
CREATE OR REPLACE FUNCTION sessionize(anytable, interval)
  RETURNS setof record
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA
  WITH (describe = sessionize_describe);

-- Recreate describe function
CREATE OR REPLACE FUNCTION sessionize_describe(internal)
  RETURNS internal
  AS '@abs_builddir@/regress@DLSUFFIX@', 'describe'
  LANGUAGE C READS SQL DATA;

-- Alter the existing function to add the describe callback
CREATE OR REPLACE FUNCTION sessionize(anytable, interval)
  RETURNS setof record
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA
  WITH (describe = sessionize_describe);

-- Observe the relationship now recorded in pg_proc_callback
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;

-- Should fail due to dependency on sessionize
DROP FUNCTION sessionize_describe(internal);
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;

-- Should cascade to main function and cleanup pg_proc_callback entries.
DROP FUNCTION sessionize_describe(internal) CASCADE;
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;
SELECT * FROM pg_proc_callback where profnoid not in (select oid from pg_proc);
SELECT * FROM pg_proc_callback where procallback not in (select oid from pg_proc);

-- Recreate both functions
CREATE OR REPLACE FUNCTION sessionize_describe(internal)
  RETURNS internal
  AS '@abs_builddir@/regress@DLSUFFIX@', 'describe'
  LANGUAGE C READS SQL DATA;

CREATE OR REPLACE FUNCTION sessionize(anytable, interval)
  RETURNS setof record
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA
  WITH (describe = sessionize_describe);

-- Check the dependency again, drop should fail
DROP FUNCTION sessionize_describe(internal);
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;

-- Alter existing function to remove the describe callback
CREATE OR REPLACE FUNCTION sessionize(anytable, interval)
  RETURNS setof record
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA;

-- Check the dependency again, drop should succeed
DROP FUNCTION sessionize_describe(internal);
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;
DROP FUNCTION sessionize(anytable, interval);

-- Shouldn't have any integrity constraint violations
SELECT * FROM pg_proc_callback where profnoid not in (select oid from pg_proc);
SELECT * FROM pg_proc_callback where procallback not in (select oid from pg_proc);

-- One more time, creating without using "OR REPLACE"
CREATE FUNCTION sessionize_describe(internal)
  RETURNS internal
  AS '@abs_builddir@/regress@DLSUFFIX@', 'describe'
  LANGUAGE C READS SQL DATA;

CREATE FUNCTION sessionize_plain(anytable, interval)
  RETURNS setof record
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA;

CREATE FUNCTION sessionize_static(anytable, interval)
  RETURNS TABLE(id integer, "time" timestamp, sessionnum integer)
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA;

CREATE FUNCTION sessionize_dynamic(anytable, interval)
  RETURNS setof record
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA
  WITH (describe = sessionize_describe);

-- Check catalog for the new functions, should only see sessionize_describe
SELECT * FROM pg_proc_callback
WHERE profnoid in (
        'sessionize_plain'::regproc,
        'sessionize_static'::regproc,
        'sessionize_dynamic'::regproc
        );

-- Plain without qualification fails
SELECT *
FROM sessionize_plain(
    TABLE
    (
        SELECT id, time
        FROM history
        ORDER BY id, time
        SCATTER BY id
    ),
    '1 minute' ) as sessionize
ORDER BY id, time; -- FAILS, no record type

-- Static with qualification fails
SELECT *
FROM sessionize_static(
    TABLE
    (
        SELECT id, time
        FROM history
        ORDER BY id, time
        SCATTER BY id
    ),
    '1 minute' ) AS sessionize(id integer, "time" timestamp, sessionnum integer)
ORDER BY id, time; -- FAIL, double qualified

-- Describe with qualification fails
SELECT *
FROM sessionize_dynamic(
    TABLE
    (
        SELECT id, time
        FROM history
        ORDER BY id, time
        SCATTER BY id
    ),
    '1 minute' ) AS sessionize(id integer, "time" timestamp, sessionnum integer)
ORDER BY id, time; -- FAIL, double qualified

-- Otherwise results should match
SELECT *
FROM sessionize_plain(
    TABLE
    (
        SELECT id, time
        FROM history
        ORDER BY id, time
        SCATTER BY id
    ),
    '1 minute' ) AS sessionize(id integer, "time" timestamp, sessionnum integer)
ORDER BY id, time; -- SUCCESS, qualified record

SELECT *
FROM sessionize_static(
    TABLE
    (
        SELECT id, time
        FROM history
        ORDER BY id, time
        SCATTER BY id
    ),
    '1 minute' ) as sessionize
ORDER BY id, time; -- SUCCESS, qualified in function

SELECT *
FROM (SELECT sessionize.id, sessionize."time", sessionize.sessionnum
      FROM sessionize_static(TABLE( SELECT history.id, history."time"
                                    FROM history
                                    ORDER BY history.id, history."time"
                                    SCATTER BY history.id),
                             '00:01:00'::interval) sessionize
      ORDER BY sessionize.id, sessionize."time") q
ORDER BY 1, 2; -- SUCCESS, qualified in function

SELECT *
FROM sessionize_dynamic(
    TABLE
    (
        SELECT id, time
        FROM history
        ORDER BY id, time
        SCATTER BY id
    ),
    '1 minute' ) as sessionize
ORDER BY id, time; -- SUCCESS, qualified in describe

-- Also test subqueries in non TABLE() parameters
SELECT *
FROM sessionize_dynamic(
    TABLE
    (
        SELECT id, time
        FROM history
        ORDER BY id, time
        SCATTER BY id
    ),
    (select '1 minute'::interval) ) as sessionize
ORDER BY id, time;

-- ===================================
-- Dynamic type interaction with VIEWS
--
--
-- Because qualification is not guaranteed stable views over functions
-- with dynamic types are *not* supported.
--
-- There are multiple ways this could cause issues:
--    - DESCRIBE function is not immutable and could return different
--      descriptors at later points in time.
--    - DESCRIBE function could have the source code change resulting
--      in a different descriptor
--    - DESCRIBE function could be replaced with a different function
--      resulting in a different descriptor.
--
-- ===================================
CREATE VIEW not_supported
AS SELECT *
FROM sessionize_dynamic(
    TABLE
    (
        SELECT id, time
        FROM history
        ORDER BY id, time
        SCATTER BY id
    ),
    '1 minute' ) as sessionize;   -- ERROR, view over dynamic typed function

-- The call is in a subquery, not the main query
CREATE VIEW not_supported
AS SELECT * FROM (
      SELECT *
      FROM sessionize_dynamic(
              TABLE
              (
                  SELECT id, time
                  FROM history
                  ORDER BY id, time
                  SCATTER BY id
              ),
              '1 minute' ) as sessionize
      ) subq;   -- ERROR, view over dynamic typed function


--
-- An existing function that has views defined over it can not be allowed to
-- be altered to have a describe function for similar reasons outlined above.
--
CREATE VIEW supported
AS SELECT *
FROM sessionize_plain(
    TABLE
    (
        SELECT id, time
        FROM history
        ORDER BY id, time
        SCATTER BY id
    ),
    '1 minute' ) as sessionize(id integer, "time" timestamp, sessionnum integer);

CREATE OR REPLACE FUNCTION sessionize_plain(anytable, interval)
  RETURNS setof record
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA
  WITH (describe = sessionize_describe);  -- ERROR: views exist

DROP VIEW supported;

-- ========================
-- The above describe is still pretty static, test a more dynamic describe function
-- ========================
CREATE FUNCTION project_describe(internal)
  RETURNS internal
  AS '@abs_builddir@/regress@DLSUFFIX@', 'project_describe'
  LANGUAGE C READS SQL DATA;

CREATE FUNCTION project(anytable, integer)
  RETURNS setof record
  AS '@abs_builddir@/regress@DLSUFFIX@', 'project'
  LANGUAGE C READS SQL DATA
  WITH (describe = project_describe);

SELECT * FROM project( TABLE( SELECT * FROM history ), 1) order by 1;
SELECT * FROM project( TABLE( SELECT * FROM history ), 2) order by 1;


-- Valid operations on results
SELECT id+1  FROM project( TABLE( SELECT * FROM history ), 1) order by 1;
SELECT extract(day from "time") FROM project( TABLE( SELECT * FROM history ), 2) order by 1;
SELECT * FROM project( TABLE( SELECT * FROM pg_am ),
  CASE 1 WHEN 2 THEN 1 ELSE GREATEST(1, COALESCE(1+1)) END);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ),
  CASE WHEN 3 IS NOT NULL AND 1 IN (1, 2) THEN floor(NULLIF(2, 3))::int END);

-- ERROR: invalid operations demonstrating different return types
SELECT extract(day from id) FROM project( TABLE( SELECT * FROM history ), 1);
SELECT "time"+1 FROM project( TABLE( SELECT * FROM history ), 2);

-- ERROR: select columns projected out by the function
SELECT id  FROM project( TABLE( SELECT * FROM history ), 2);
SELECT "time" FROM project( TABLE( SELECT * FROM history ), 1);

/* Invalid projection position */
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), NULL);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), -1);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), 0);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), 100);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), (ARRAY[2,3])[1]);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), (ROW(1, '')::example_r).a);

-- ========================
-- User data exmaple
-- ========================
CREATE FUNCTION ud_describe(internal) RETURNS internal
  AS '@abs_builddir@/regress@DLSUFFIX@', 'userdata_describe'
  LANGUAGE C READS SQL DATA;

CREATE FUNCTION ud_project(anytable) RETURNS setof RECORD
  AS '@abs_builddir@/regress@DLSUFFIX@', 'userdata_project'
  LANGUAGE C READS SQL DATA
  WITH (describe = ud_describe);

SELECT * FROM ud_project( TABLE( SELECT * FROM history ) );

-- ========================
-- Passing input without modification
-- ========================
CREATE FUNCTION noop_project(anytable) RETURNS setof RECORD
  AS '@abs_builddir@/regress@DLSUFFIX@'
  LANGUAGE C READS SQL DATA;

SELECT * FROM noop_project( TABLE( SELECT * FROM history ) ) AS s (id integer, time timestamp);
-- SCATTER BY vs MEDIAN
SELECT * FROM noop_project( TABLE( SELECT median(id) FROM history SCATTER BY median(id) ) ) AS s (med float);
SELECT * FROM noop_project( TABLE( SELECT median(id) FROM history SCATTER BY count(*) ) ) AS s (med float);
SELECT * FROM noop_project( TABLE( SELECT count(*) FROM history SCATTER BY median(id) ) ) AS s (cnt bigint);
SELECT * FROM noop_project( TABLE( SELECT count(*) FROM history GROUP BY time SCATTER BY median(id) ) ) AS s (cnt bigint);

-- ========================
-- More negative test cases
-- ========================

-- explicit return type not suitable for dynamic type resolution
CREATE FUNCTION x() returns int
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA
  WITH (describe = sessionize_describe);

-- explicit return type (setof) not suitable for dynamic type resolution
CREATE FUNCTION x() returns setof int
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA
  WITH (describe = sessionize_describe);

-- explicit return type (TABLE) not suitable for dynamic type resolution
CREATE FUNCTION x() returns TABLE(id integer, "time" timestamp, sessionnum integer)
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA
  WITH (describe = sessionize_describe);

-- explicit return type (OUT PARAMS) not suitable for dynamic type resolution
CREATE FUNCTION x(OUT id integer, OUT "time" timestamp, OUT sessionnum integer)
  AS '@abs_builddir@/regress@DLSUFFIX@', 'sessionize'
  LANGUAGE C READS SQL DATA
  WITH (describe = sessionize_describe);

-- ========================
-- Interaction with PREPARE
-- ========================

-- PASS: completely defined at prepare time
PREPARE p1 AS SELECT * FROM multiset_2( TABLE(SELECT * FROM example_r) ) order by a, b;
EXECUTE p1;
DEALLOCATE p1;

-- FAIL: can't pass anytable as prepare argument
PREPARE p2 AS SELECT * FROM multiset_2( $1 ) order by a, b;
-- This actually passes currently. You can't do anything useful with the prepared
-- statement though, as anytable's input function just throws an error.
PREPARE p3(anytable) AS SELECT * FROM multiset_5( $1 );

-- FAIL: $1 is not a constant
PREPARE p4 AS SELECT * FROM project( TABLE( SELECT * FROM pg_am ), $1);
PREPARE p5(integer) AS SELECT * FROM project( TABLE( SELECT * FROM pg_am ), $1);

-- PASS: We allow table functions with describe to be prepared.  This can
-- theoretically require need for reparse/replan, but that requires more
-- general support of prepared statements in conjunction with a plan cache.
PREPARE p6 AS SELECT * FROM project( TABLE( SELECT * FROM example_r ), 2) ORDER BY 1;
EXECUTE p6;

-- PASS: Check that the prepared statement is re-planned when the underlying
-- table changes.
ALTER TABLE example_r ALTER COLUMN a TYPE numeric;
EXECUTE p6;
DEALLOCATE p6;

-- =======
-- Cleanup
-- =======
DROP SCHEMA table_function CASCADE;
SET search_path TO public;
